%% Created: 2012-4-5
%% Description: TODO: Add description to GossipNode
-module(gossip_node).

%%
%% Include files
%%

%%
%% Exported Functions
%%
-export([init/1]).

%%
%% API Functions
%%

%%
%% Entry point for gossip_node process, passed a unique ID in the network as the identification for each node.
%% Wait until "start" or "stop" message received. In the "start" message, the PID of the monitor process as well
%% as all the neighbors PIDs and this nodes initial configuration are included.
%%
%% Parameters:
%% 	ID: unique ID for this node
%%
init(ID) ->
	gossip:init_random(),
	receive
		{start, _MainPid, MonitorPid, {AllNodes, GossipData}} ->
			Neighbours = [{NID, _NPid} || {NID, _NPid} <- AllNodes, NID/=ID],
			loop(MonitorPid, {ID, self()}, Neighbours, GossipData);
		
		{stop} ->
			ok
	end.

%% 		{timeout} ->
%% 			% active receive timeout {{0, 0, 0, 1}, {0, 0}}
%% 			%io:format("R(~p): timeout...~n", [Node]),
%% 			MonitorPid ! {stat, element(1, Node), {{0, 0, 0, 1}, {0, 0}}},
%% 			loop(MonitorPid, Node, Neighbours, GossipData);
	

%%
%% Loop used after sending a gossip request to wait for an acknowledgement from the receiver. 
%%
%% Different types of received messages are handled:
%%	"ok" message: correct acknowledgement, update this nodes configuration and go back to the main loop.
%% 	"reject" message: rejected by the receiver, the sended request failed, go back to the main loop.
%%	REQUEST from other nodes: send reject, and continue to wait the response.
%%	"stop" message: shutdown process, return "ok".
%% 
%% In handling of the different types of messages, different stat information is sent to the gossip_monitor.
%% Format: 
%% 	{{ActiveSend, ActiveReceiveNormal, ActiveReceiveReject, ActiveReceiveTimeout}, {PassiveSendNormal, PassiveSendReject}}
%%
%% Parameters:
%% 	MonitorPid: PID for the gossip_monitor
%%	Node: {ID, PID} for the current node
%% 	ToNode: {ID, PID} for the receiver node
%%	Neighbours: Array of {ID, PID} for the neighbours
%% 	GossipData: Internal data for the current node, here means the CPU configuration
%%
wait_send_response(MonitorPid, Node, ToNode, Neighbours, GossipData) ->
	receive
		{response, ToNode, ok, GossipDataFromReply} ->
			% active receive normal {{0, 1, 0, 0}, {0, 0}}
			GossipDataNewSent = GossipDataFromReply,						
			%io:format("R(~p): response(ok), ~p (~p) . [~p=>~p]... ~n", [Node, ToNode, GossipDataFromReply, GossipData, GossipDataNewSent]),
			MonitorPid ! {stat, element(1, Node), {{0, 1, 0, 0}, {0, 0}}},
			MonitorPid ! {report, element(1, Node), GossipDataNewSent},
			loop(MonitorPid, Node, Neighbours, GossipDataNewSent);
		
		{response, ToNode, reject} ->
			% active receive reject {{0, 0, 1, 0}, {0, 0}}
			%io:format("R(~p): response(rj), ~p ... ~n", [Node, ToNode]),
			MonitorPid ! {stat, element(1, Node), {{0, 0, 1, 0}, {0, 0}}},
			loop(MonitorPid, Node, Neighbours, GossipData);
	
		{request, FromNodeReject, _GossipDataFrom} ->
			%io:format("R(~p): request(rj), ~p... ~n", [Node, FromNodeReject]),
			% passive send reject  {{0, 0, 0, 0}, {0, 1}}
			element(2, FromNodeReject) ! {response, Node, reject},
			MonitorPid ! {stat, element(1, Node), {{0, 0, 0, 0}, {0, 1}}},
			wait_send_response(MonitorPid, Node, ToNode, Neighbours, GossipData);
		
		{stop} ->
			ok
	end.

%%
%% Main loop for the gossip_node, node is first waiting for a randomed period of time for the gossip request,
%% if received, this nodes configuration is updated and an acknowledgement is sent and then continue the loop;
%% if timeout, the node will randomly select a neighbour to send the gossip request, and then enter wait_send_response.
%% Stat information is also sent to gossip_monitor when there is any send ORreceive activities. The same format is used for stats.
%%
%% Different types of received messages are handled:
%%	REQUEST from other nodes: update this nodes configuration, send acknowledgement, and continue the loop.
%%	"stop" message: shutdown process, return "ok".
%% 
%% Parameters:
%% 	MonitorPid: PID for the gossip_monitor
%%	Node: {ID, PID} for the current node
%%	Neighbours: Array of {ID, PID} for the neighbours
%% 	GossipData: Internal data for the current node, here means the CPU configuration
%%
loop(MonitorPid, Node, Neighbours, GossipData) ->
	Timeout = get_time_out(length(Neighbours)+1),
	%Timeout = 200,
	%io:format("~p Timeout ~p!... ~n", [Node, Timeout]),
	receive
		{request, FromNode, GossipDataFrom} ->
			GossipDataNewReceived = (GossipData + GossipDataFrom) div 2,
			%io:format("R(~p): request(ok), ~p (~p). [~p=>~p]... ~n", [Node, FromNode, GossipDataFrom, GossipData, GossipDataNewReceived]),			
			element(2, FromNode) ! {response, Node, ok, GossipDataNewReceived},
			
			% passive send normal  {{0, 0, 0, 0}, {1, 0}}
			MonitorPid ! {stat, element(1, Node), {{0, 0, 0, 0}, {1, 0}}},
			MonitorPid ! {report, element(1, Node), GossipDataNewReceived},
			loop(MonitorPid, Node, Neighbours, GossipDataNewReceived);
		
		{stop} ->
			ok
	after
			Timeout ->
				% active send {{1, 0, 0, 0}, {0, 0}}
				ToNode = select_neighbour(Neighbours),
				%io:format("S(~p), ~p(~p)...~n", [Node, ToNode, GossipData]),
				element(2, ToNode) ! {request, Node, GossipData},		
				MonitorPid ! {stat, element(1, Node), {{1, 0, 0, 0}, {0, 0}}},
				wait_send_response(MonitorPid, Node, ToNode, Neighbours, GossipData)
	end.

get_time_out(Size) ->
	MaxBound = 2000,
	MinBound = 200,
	Bound = erlang:min(MaxBound,erlang:max(MinBound, Size*10)),
	random:uniform(Bound).

select_neighbour(Neighbours) ->
	lists:nth(random:uniform(length(Neighbours)), Neighbours).
